using System;
using System.Collections;
using Evaluant.OPath;
using System.Globalization;
using System.Threading;

using Evaluant.Uss.Commands;
using Evaluant.Uss.Common;

namespace Evaluant.Uss.Memory
{
    /// <summary>
    /// Loads everything into memory for better performances. If no _Delegator is provided, nothing is persisted.
    /// </summary>
    /// <remarks>
    /// This engine is thread safe as all the entities are shared among several engines using the same factory.
    /// The factory owns the entities.
    /// </remarks>
    public class MemoryEngine : PersistenceEngineImplementation
    {
        protected Hashtable _Entities;
        protected ReaderWriterLock _RWL;

        public MemoryEngine(IPersistenceEngine delegator, Hashtable entities, ReaderWriterLock rwl, Models.Model model, CultureInfo culture)
            : base()
        {
            _Delegator = delegator;
            _Entities = entities;
            _RWL = rwl;
            _Model = model;
            Culture = culture;
            _CommandProcessor = new MemoryCommandProcessor(_Entities, _RWL);
        }

        ICommandProcessor _CommandProcessor;
        public ICommandProcessor CommandProcessor
        {
            get { return _CommandProcessor; }
            set { _CommandProcessor = value; }
        }

        private IPersistenceEngine _Delegator;
        public IPersistenceEngine Delegator
        {
            get { return _Delegator; }
            set { _Delegator = value; }
        }

        public override void InitializeRepository()
        {
            _RWL.AcquireWriterLock(Timeout.Infinite);
            try
            {
                if (_Delegator != null)
                    _Delegator.InitializeRepository();

                _Entities.Clear();
            }
            finally
            {
                _RWL.ReleaseWriterLock();
            }
        }

        public override void Initialize()
        {
            if (_Delegator != null)
                _Delegator.Initialize();
        }

        public override void LoadReference(IEnumerable entities, string[] references)
        {
            _RWL.AcquireReaderLock(Timeout.Infinite);
            try
            {
                foreach (Entity entity in entities)
                {
                    State tempState = entity.State;

                    entity.RemoveReference(references);

                    Entity local = _Entities[Utils.GetCacheKey(entity)] as Entity;

                    foreach (EntityEntry ee in local)
                    {
                        if (ee.IsEntity)
                        {
                            if (references.Length == 0 || Array.IndexOf(references, ee.Name) != -1)
                            {
                                Entity refEntity = ((Entity)ee.Value).Clone(new string[0]);
                                entity.AddValue(ee.Name, refEntity, typeof(Entity), State.UpToDate);

                                refEntity.State = State.UpToDate;
                            }
                        }
                    }

                    entity.State = tempState;

                    // mark references as loaded for each entity
                    foreach (string refName in references)
                        if (!entity.InferredReferences.Contains(refName))
                            entity.InferredReferences.Add(refName);
                }
            }
            finally
            {
                _RWL.ReleaseReaderLock();
            }
        }

        public override EntitySet Load(OPathQuery query, string[] attributes, string orderby, int first, int max)
        {
            if (first <= 0)
                throw new ArgumentException("first must be greater than 0");

            if (max < 0)
                throw new ArgumentException("max must be none negative");

            _RWL.AcquireReaderLock(Timeout.Infinite);
            try
            {
                // Extract the first identifier
                string type = query.Path.Identifiers[0].Value;

                EntitySet roots = new EntitySet();
                string[] rootsTypes = Model.GetTreeAsArray(type);

                foreach (Entity entity in _Entities.Values)
                {
                    if (Array.IndexOf(rootsTypes, entity.Type) != -1)
                        roots.Add(entity);
                }

                // We get a cloned opath at the compile time so we can modify it
                query.Path.Identifiers[0].Value = "this";

                Navigator navigator = new Navigator(roots);

                EntitySet result = new EntitySet();
                foreach (Entity e in navigator.SelectElements(query.Path))
                {
                    result.Add(e);
                }

                EntitySet entityset = new EntitySet();

                foreach (Entity e in result)
                {
                    Entity clone = e.Clone(attributes);

                    clone.State = State.UpToDate;
                    entityset.Add(clone);
                }

                // sort
                if (orderby != null)
                    entityset.Sort(orderby.Split(','));

                // page
                EntitySet pageResult = new EntitySet();
                Utils.TruncateList(entityset, pageResult, first, max);

                return pageResult;
            }
            finally
            {
                _RWL.ReleaseReaderLock();
            }
        }

        public override EntitySet LoadWithId(string type, string[] ids, string[] attributes)
        {
            _RWL.AcquireReaderLock(Timeout.Infinite);
            try
            {
                EntitySet result = new EntitySet();

                foreach (string id in ids)
                {
                    foreach (string subtype in Model.GetTreeAsArray(type))
                    {
                        bool found = false;
                        if (_Entities.Contains(Utils.GetCacheKey(subtype, id)))
                        {
                            Entity local = _Entities[Utils.GetCacheKey(subtype, id)] as Entity;
                            Entity entity = local.Clone(attributes);

                            entity.MetaData = Entity.LoadMetaData.AttributesLoaded;
                            entity.State = State.UpToDate;
                            result.Add(entity);

                            found = true;
                        }

                        if (found)
                            break;
                    }
                }

                return result;
            }
            finally
            {
                _RWL.ReleaseReaderLock();
            }

        }

        public override object LoadScalar(OPathQuery query)
        {
            _RWL.AcquireReaderLock(Timeout.Infinite);

            try
            {
                EntitySet roots = new EntitySet();
                foreach (Entity entity in _Entities.Values)
                    roots.Add(entity);

                NavigationVisitor eval = new NavigationVisitor(roots, Model, true);
                eval.Visit(query.Expression);

                object res = eval._NodeValue;

                // Ensures the result in an Int32 in case of a count()
                if (query.Expression.Operands[0] is OPath.Expressions.Function)
                    res = Convert.ToInt32(res);

                return res;
            }
            finally
            {
                _RWL.ReleaseReaderLock();
            }
        }

        public override object LoadScalar(string opath)
        {
            _RWL.AcquireReaderLock(Timeout.Infinite);

            try
            {
                opath = string.Format("eval({0})", opath);
                OPathQuery query = new OPathQuery(opath, OPathQueryTypeEnum.Expression);
                query.Compile();

                if (query.HasErrors)
                    throw new OPathException(query);

                EntitySet roots = new EntitySet();
                foreach (Entity entity in _Entities.Values)
                    roots.Add(entity);

                NavigationVisitor eval = new NavigationVisitor(roots, Model, true);
                eval.Visit(query.Expression);

                object res = eval._NodeValue;

                // Ensures the result in an Int32 in case of a count()
                if (opath.Trim().StartsWith("eval(count"))
                    res = Convert.ToInt32(res);

                return res;
            }
            finally
            {
                _RWL.ReleaseReaderLock();
            }
        }

        public override void BeforeProcessCommands(Transaction tx)
        {
            _RWL.AcquireWriterLock(Timeout.Infinite);

            try
            {
                base.BeforeProcessCommands(tx);

                if (_Delegator != null)
                    _Delegator.BeforeProcessCommands(tx);
            }
            catch
            {
                // Releases the lock if an error occured
                _RWL.ReleaseWriterLock();
                throw;
            }
        }


        public override void ProcessCommands(Transaction tx)
        {
            try
            {
                foreach (Command command in tx.PendingCommands)
                    command.Accept(_CommandProcessor);

                if (_Delegator != null)
                    _Delegator.ProcessCommands(tx);
            }
            catch
            {
                // Releases the lock if an error occured
                _RWL.ReleaseWriterLock();
                throw;
            }
        }


        public override void AfterProcessCommands(Transaction tx)
        {
            try
            {
                base.AfterProcessCommands(tx);

                if (_Delegator != null)
                    _Delegator.AfterProcessCommands(tx);
            }
            finally
            {
                // Releases the lock if an error occured
                _RWL.ReleaseWriterLock();
            }
        }
    }
}
